<?php
/**
 * B2Core 是由 Brant (brantx@gmail.com)发起的基于PHP的MVC架构
 * 核心思想是在采用MVC框架的基础上最大限度的保留php的灵活性
 * */
define('B2CORE_VERSION','3.0');

// 添加环境变量定义
if (!defined('ENV')) {
    define('ENV', 'development'); // 可以设置为 'development' 或 'production'
}

// 载入配置文件：数据库、url路由等等
require(APP.'config.php');

// 如果配置了数据库则载入
if(isset($db_config)){
  $db = new db($db_config);
}

// 获取请求的地址兼容 SAE
$uri = '';
if(isset($_SERVER['PATH_INFO'])) $uri = $_SERVER['PATH_INFO'];
elseif(isset($_SERVER['ORIG_PATH_INFO'])) $uri = $_SERVER['ORIG_PATH_INFO'];
elseif(isset($_SERVER['QUERY_STRING'])){
  $ss = explode('&',$_SERVER['QUERY_STRING']);
  $uri = $ss[0];
}

if (defined('STDIN')) {
  $args = array_slice($_SERVER['argv'], 1);
  $uri = $args ? '/' . implode('/', $args) . '/' : '';
}

render_url();
function render_url()
{
  // redirect abc/def to abc/def/ to make SEO url
  global $uri;
  if(strpos($uri,'.'))return;
  if($_SERVER['QUERY_STRING'])return;
  if(substr($uri,-1)=='/')return;
  if($uri =='')return;
  header("HTTP/1.1 301 Moved Permanently");
  header ('Location:'.$_SERVER['REQUEST_URI'].'/');
  exit(0);
}

// $_POST = array_map('addslashes',$_POST);
// 修正: 未判断多维数组, 会返回 null
function addslashes_byref(&$var)
{
  $var = addslashes($var);
}
// array_walk_recursive($_POST, 'addslashes_byref');

//echo $uri;
//print_r($_SERVER);
//echo ' 去除Magic_Quotes';
if(version_compare(PHP_VERSION, '7.4.0', '<')) {
  if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
    function stripslashes_deep($value)
    {
      $value = is_array($value) ? array_map('stripslashes_deep', $value) : (isset($value) ? stripslashes($value) : null);
      return $value;
    }
    $_POST = stripslashes_deep($_POST);
    $_GET = stripslashes_deep($_GET);
    $_COOKIE = stripslashes_deep($_COOKIE);
  }
}

// 执行 config.php 中配置的url路由
foreach ($route_config as $key => $val)
{
  $key = str_replace(':any', '([^\/.]+)', str_replace(':num', '([0-9]+)', $key));
  if (preg_match('#^'.$key.'#', $uri))$uri = preg_replace('#^'.$key.'#', $val, $uri);
}

//echo ' 获取URL中每一段的参数';
$uri = rtrim($uri,'/');
$seg = explode('/',$uri);
$des_dir = $dir = '';

/* 依次载入控���器上级所有目录的架构文件 __construct.php
* 架构文件可以包含当前目录下的所有控制器的父类，和需要调用的函数
*/

foreach($seg as $cur_dir)
{
  $des_dir.=$cur_dir."/";
  if(is_file(APP.'c'.$des_dir.'__construct.php')) {
    require(APP.'c'.$des_dir.'__construct.php');
    $dir .=array_shift($seg).'/';
  }
  else {
    break;
  }
}

/* 根据 url 调用控制器中的方法，如果不存在返回 404 错误
* 默认请求 class home->index()
*/
//echo  '默认请求 class home->index()';

$msg_404 = '页面不存在或已删除, 稍后为您返回首页.';
$dir = $dir ? $dir:'/';
array_unshift($seg,NULL);
$class  = isset($seg[1])?$seg[1]:'home';
$method = isset($seg[2])?$seg[2]:'index';
// 开始改造
/* 接受一切地址 */
if(!is_file(APP.'c'.$dir.$class.'.php')) {
  //if (ENV == 'development') show_404( 'file:'.APP.'c'.$dir.$class.'.php');
  if($dir == '/')$class = 'entity';
  else show_404( $msg_404 );
}
require(APP.'c'.$dir.$class.'.php');
if(!class_exists($class)) {
  if (ENV == 'development') show_404('class_not_exists:'.$class);
  show_404( $msg_404 );
}
if(!method_exists($class,$method)) {
  if (ENV == 'development') show_404('method_not_exists:'.$class.'->'.$method);
  show_404( $msg_404 );
//    $method = 'ext';
}
$B2 = new $class();
call_user_func_array(array(&$B2, $method), array_slice($seg, 3));

/* B2 系统函数
* load($path,$instantiate) 可以动态载入对象，如：控制器、Model、库类等
* $path 是类文件相对 app 的地址
* $instantiate 为 False 时，仅引用文件，不实例化对象
* $instantiate 为数组时，数组内容会作为参数传递给对象
*/

function &load($path, $instantiate = TRUE )
{
  $param = FALSE;
  if(is_array($instantiate)) {
    $param = $instantiate;
    $instantiate = TRUE;
  }
  $file = explode('/',$path);
  $class_name = array_pop($file);
  $object_name = md5($path);

  static $objects = array();
  if (isset($objects[$object_name])) {
    if($objects[$object_name] == TRUE && $instantiate == TRUE) {
      if ($param == FALSE) return new $class_name();
      return new $class_name($param);
    }
    return $objects[$object_name];
  }
  require(APP.$path.'.php');
  if ($instantiate == FALSE) $objects[$object_name] = TRUE;
  elseif ($param) $objects[$object_name] = new $class_name($param);
  else  $objects[$object_name] = new $class_name();
  return $objects[$object_name];
}

// 取得 url 的片段，如 url 是 /abc/def/g/  seg(1) = abc
function seg($i)
{
  global $seg;
  return isset($seg[$i])?$seg[$i]:false;
}

function lang($key){
  global $lang;
  return isset($lang[$key])?$lang[$key]:false;
}

/* 调用 view 文件
* function view($view,$param = array(),$cache = FALSE)
* $view 是模板文件相对 app/v/ 目录的地址，地址应去除 .php 文件后缀
* $param 数组中的变量会传递给模板文件
* $cache = TRUE 时，不像浏览器输出结果，而是以 string 的形式 return
*/
function view($view,$param = array(),$cache = FALSE)
{
  if(!empty($param))extract($param);
  ob_start();
  if(is_file(APP.$view.'.php')) {
    require APP.$view.'.php';
  }
  else {
    echo 'view '.$view.' doesn\'t exsit';
    return false;
  }
  // Return the file data if requested
  if ($cache === TRUE)
  {
    $buffer = ob_get_contents();
    @ob_end_clean();
    return $buffer;
  }
}

// 写入日志
function write_log($level = 0 ,$content = 'none')
{
  file_put_contents(APP.'log/'.$level.'-'.date('Y-m-d').'.log', $content , FILE_APPEND );
}

//echo ' 显示404错误';
function show_404($msg = '') //显示 404 错误
{
  header("HTTP/1.1 404 Not Found");
  $param = array('url'=>'/', 'msg'=>'404:', 'ext_msg'=>$msg , 'sec'=>5);
  view('v/redirect', $param);
  exit(1);
}

/*  B2Core 系统类 */
// 抽象的控制器类，建议所有的控制器均基层此类或者此类的子类
class c {
  function index()
  {
    echo "基于 B2 v".VERSION." 创建";
  }
}

class db {
  var $link;
  var $last_query;
  private $current_db;
  
  function __construct($conf = null)
  {
    try {
      $this->link = mysqli_connect(
        $conf['host'], 
        $conf['user'], 
        $conf['password']
      );
      
      if (!$this->link) {
        throw new Exception('数据库连接失败: ' . mysqli_connect_error());
      }

      // 检查数据库是否存在
      $result = mysqli_query($this->link, "SHOW DATABASES LIKE '{$conf['default_db']}'");
      if (!$result || mysqli_num_rows($result) == 0) {
        // 尝试创建数据库
        $create_db_sql = "CREATE DATABASE IF NOT EXISTS `{$conf['default_db']}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
        if (!mysqli_query($this->link, $create_db_sql)) {
          throw new Exception("无法创建数据库 {$conf['default_db']}: " . mysqli_error($this->link));
        }
      }

      // 选择数据库
      if (!mysqli_select_db($this->link, $conf['default_db'])) {
        throw new Exception("无法选择数据库 {$conf['default_db']}: " . mysqli_error($this->link));
      }
      
      $this->current_db = $conf['default_db'];
      mysqli_query($this->link, 'set names utf8mb4');
      
    } catch (Exception $e) {
      if (ENV == 'development') {
        die('数据库错误: ' . $e->getMessage());
      } else {
        die('数据库连接错误,请联系管理员');
      }
    }
  }

  //执行 query 查询，如果结果为数组，则返回数组数据
  function query($query)
  {
    // 确保已选择数据库
    if (!$this->current_db) {
      throw new Exception('未选择数据库');
    }
    
    $query = $this->strip_evil_data($query, FALSE);
    $ret = array();
    $this->last_query = $query;
    $result = mysqli_query($this->link, $query);
    
    if (!$result) {
      if (ENV == 'development') {
        die("DB Error: " . mysqli_error($this->link) . "\nQuery: " . $query);
      } else {
        die("数据库查询错误");
      }
    }
    
    if($result === TRUE) return TRUE;
    
    while($record = mysqli_fetch_assoc($result)) {
      $ret[] = $record;
    }
    return $ret;
  }

  function insert_id() {return mysqli_insert_id($this->link);}

  // 执行多条 SQL 语句
  function muti_query($query)
  {
    $sq = explode(";\n",$query);
    foreach($sq  as $s){
      if(trim($s)!= '')$this->query($s);
    }
  }

  function escape($str){
    //$str = htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    return mysqli_real_escape_string($this->link,$str);
  }

  // LIKE '%nn'(n为数字) %nn 会作为控制字符被过滤掉, 需要注意
  function strip_evil_data($str, $xss_clean = TRUE)
  {
    if (is_array($str)) {
      $new_array = array();
      foreach ($str as $key => $val) {
        $new_array[$this->strip_evil_keys($key)] = $this->strip_evil_data($val);
      }
      return $new_array;
    }

    // PHP 5.4 get_magic_quotes_gpc() 始终返回 0, 未来版本可能会被去掉
    if ( version_compare(PHP_VERSION, '5.4') < 0 && get_magic_quotes_gpc()) {
      $str = stripslashes($str);
    }

    if(preg_match('/[^\x00-\x7F]/S', $str) != 0) {
      $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
    }

    // 去掉控制字符
    $non_displayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15
    $non_displayables[] = '/%1[0-9a-f]/'; // url encoded 16-31
    $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127
    do {
      $str = preg_replace($non_displayables, '', $str, -1, $count);
    } while ($count);

    // 实现一个 xss 过滤
    if ($xss_clean) {
      $str = htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // 统一换行符
    if (strpos($str, "\r") !== FALSE) {
      $str = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str);
    }

    return $str;
  }

  function strip_evil_keys($str)
  {
    if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str)) {
      exit('非法字符');
    }
    if(preg_match('/[^\x00-\x7F]/S', $str) != 0) {
      $str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
    }
    return $str;
  }
}

// 模块类，封装了通用CURD模块操作，建议所有模块都继承此类。
class m {
  public $db;
  public $table;
  public $filter = 1;
  public $fields;
  public $key;
  public $selected = array();

  function __construct($table = null)
  {
    global $db;
    $this->db = $db;
    $this->table = $table;
    $this->key = 'uuid';
  }

  public function __call($name, $arg) {
    return call_user_func_array(array($this, $name), $arg);
  }

  // 向数据库插入数组格式数据
  function add($elem = FALSE)
  {

    $query_list = array();
    if(!$elem)$elem = $_POST;
    foreach($this->fields as $f) {
      if(isset($elem[$f])){
        $elem[$f] = $this->db->escape(is_array($elem[$f])?_encode($elem[$f]):$elem[$f]);
        $query_list[] = "`$f` ";
        $query_list1[] = "'$elem[$f]'";
      }
    }
    $query = "insert into `$this->table` (".implode(',',$query_list).") values (".implode(',',$query_list1).")";
    $this->db->query($query);

    if(in_array('uuid',$this->fields)) return $elem['uuid'];
    return $this->db->insert_id();
  }

  // 批量插入数据, 非数组按字段名合并入全部已有数组, 数组按字段名插入, **不支持多维数组**
  function bunch_add($elem = FALSE)
  {
    $columns = $data = array();
    !$elem && $elem = $_POST;
    foreach ($elem as $e) {
      if (is_array($e)) {
        $data_label = $data_row = array();
        foreach ($this->fields as $f) {
          if(isset($e[$f])){
            if(!in_array($f,$data_label))$data_label[] = $f;
            $data_cell = $this->db->escape($e[$f]);
            $data_row[] = "'$data_cell'";
          }
        }
        $data[] = "(" . implode(",", $data_row) . ")";
      }
    }

    if (empty($data_label) || empty($data)) {
      return false;
    }

    $query = "INSERT INTO `$this->table` (" . implode(",", $data_label) . ") VALUES " . implode(",", $data);
    $this->db->query($query);
    return $this->db->insert_id();
  }

  // 删除某一条数据
  function del($id)
  {
    $this->db->query("delete from `$this->table` where ".$this->filter." and ".$this->key."='$id'");
  }

  // 更新数据
  function update($id = 0, $elem = FALSE)
  {
    $query_list = array();
    if(!$elem)$elem = $_POST;
    foreach($this->fields as $f) {
      if(isset($elem[$f])){
        $elem[$f] = is_array($elem[$f])?$this->db->escape(_encode($elem[$f])):$this->db->escape($elem[$f]);
        $query_list[] = "`$f` = '$elem[$f]'";
      }
    }
    $this->db->query("update `$this->table` set ".implode(',',$query_list)." where ".$this->filter." and ".$this->key." ='$id'" );
  }

  // 统计数量
  function count($where='')
  {
    $res =  $this->db->query("select count(*) as a from `$this->table` where ".$this->filter."  $where");
    return $res[0]['a'];
  }

  public function select($field, $as = false)
  {
    $query = $as ? $field . ' AS ' . $as : $field;
    array_push($this->selected, $query);

    return $this;
  }

  private function _build_select()
  {
    $sql = $this->selected ? implode(', ', $this->selected) : '*';
    $this->selected = array();
    return $sql;
  }

  /* get($id) 取得一条数据 或
  *  get($postquery = '',$cur = 1,$psize = 30) 取得多条数据
  */
  function get()
  {
    $args = func_get_args();
    if(is_numeric($args[0])) return $this->__call('get_one', $args);
    return $this->__call('get_many', $args);
  }

  function get_one($id = 0)
  {
    $select_sql = $this->_build_select();
    $res =  $this->db->query("select $select_sql from `$this->table` where ".$this->filter." and  ".$this->key."='$id'");
    if(isset($res[0]))return $res[0];
    return false;
  }

  function get_many($postquery = '',$cur = 1,$psize = 30)
  {
    $cur = $cur > 0 ?$cur:1;
    $start = ($cur - 1) * $psize;
    $select_sql = $this->_build_select();
    $query = "select $select_sql from `$this->table` where  ".$this->filter." $postquery limit $start , $psize";
    return $this->db->query($query);
  }
}


function __msg($str){
  header("Content-type: text/html; charset=utf-8");
  echo '<!DOCTYPE html><head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link href="http://lib.sinaapp.com/js/bootstrap/2.3.2/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    </head><body><div class="hero-unit" >';
  echo $str;
  echo '</div></body></html>';
  exit(1);
}
